RASPBERRY PI MULTI-GAME ARCADE  

Alex Kluver(abk98), Ian Ress (ir232), Richard Seibert (rs2585)


Objective: 

The goal of this project was to build a portable gaming device based around the Raspberry Pi, PiTFT, and a joystick module. On this device, the group wanted users to have the ability to play either Pong against an AI computer, Brickbreaker, or Tetris.


Project Video:


Introduction:

For this project, our team created a miniature arcade with three different games. We implemented Tetris, Brick Breaker, and Pong completely from scratch utilizing python and the pygame library. To create a cohesive gaming experience, we also created a game launcher home screen for these three games. The PiTFT was utilized as the display for this arcade and the on-board buttons are used for game control. Pong and Brick Breaker incorporate an I2C joystick, which is used for paddle movement. Tetris uses the PiTFT buttons to control the pieces. The Raspberry Pi, PiTFT, and Joystick are also all fitted into a 3D printed case for the optimal gaming experience.


Design:

Objectives:

Hardware objectives revolved around the design and development of a custom case to encapsulate all of the necessary components, including: the Raspberry Pi, PiTFT, PiTFT buttons, joystick module, and a LiPo battery. Software objectives revolved around the creation of the user interface, logic, and level difficulty framework for the three games. This additionally included a main home screen where users have the ability to select either one of these three games.

Hardware Design: 

The case was designed for 3D printing, however slight modifications can be made in order to make it compatible with injection molding. Autodesk Fusion360 was used to create the 3D model of the case and assembly, as well as the necessary STL files for 3D printing. Everything was printed on an Ultimaker S5 using standard PLA. The main goal of the design was to encapsulate a LiPo battery, joystick module, PiTFT, PiTFT buttons, Raspberry Pi, wiring connections, and cables into a compact and portable case.

A bottom plate, middle plate, and top plate formed the entire case. The bottom plate housed the LiPo battery and had an opening for the battery’s USB cable. The middle plate housed the Raspberry Pi, PiTFT, PiTFT buttons, and the joystick module. Threaded inserts were fit into mounting holes in order to screw in the Raspberry Pi and joystick module to this section of the case. The PiTFT was then mounted on top of the Raspberry Pi using the already soldered-on pin headers, as well as additional offsets that had threads to screw the top plate onto. The top plate was then screwed onto the PiTFT. This also had necessary openings for the joystick and PiTFT buttons. Images of this assembly are included in the Drawings and Parts List section.

The Joystick module used I2C and was wired to SDA3 (pin 3) and SCL3 (pin 5) of the Raspberry Pi. 3.3V (pin 1) and ground (pin 6) supplied power to the module.

Software Design:

A separate module was created for each game (Pong, Brickbreaker, Tetris). These three modules contained the code for the creation of the user interface on the PiTFT and the overarching game logic. Within each module, a method named run() exists. If this method is called, the game actually runs on the Raspberry Pi.

Main.py housed the main application code. Three instances of the Pong, Brickbreaker, and Tetris modules are created. A home screen is then created where the user is able to choose between these three games. Within the main while loop, the user input is constantly polled until one of the games is selected. When this occurs, the run() method within the selected module is executed and the game begins. Organizing the software this way made it easy to separately develop each game, and combine everything together in a home screen. This made the development process much easier in the long run.

The Pong, Brickbreaker, and Tetris modules as well as main.py comprised the main application code. However this project also includes lower level software layers that are shown in the figure below. On the first layer is the actual hardware including the Raspberry Pi, PiTFT, and Joystick module. The software that directly interfaces with this hardware is known as a Hardware Abstraction Layer. For this project, this includes the Raspberry Pi GPIO library to detect button presses on the PiTFT and the Joystick module library supplied by Sparkfun to read the raw data values from the joystick. The libraries themselves are written in low level code in order to interact directly with the necessary hardware registers. However the libraries provide functions that can be called to provide all of the necessary hardware interaction capabilities while abstracting this low level code away. So within the game modules and main.py, these HAL functions are called to poll the PiTFT buttons and detect where the joystick is currently located. For the actual user interface, Pygame is used to control the PiTFT. This library provides similar abstraction capabilities in order for rapid user interface development.

Software Layers and Structure


Testing:

Case:

Three iterations of the case were created and tested for fits. These iterations were necessary for two reasons: (1) tolerance errors experienced due to the nature of 3D printing and (2) design errors not originally accounted for in the assemblies. Changes in the first iteration were mainly due to reason (2). Certain mounting holes and ribs interfered with the actual through-hole connections, and surface mount chips on the joystick module. This was not accounted for in the original model and was changed in the second iteration. Additional stabilizers were also created in order for the Raspberry Pi and PiTFT to not deform downwards when the user presses the PiTFT buttons. Finally, the joystick was mounted higher.

The second iteration was tested and changes needed to be made mainly for reason (1). The tolerances were off for the Raspberry Pi power cable opening, top plate screw holes, and PiTFT button heights. These were changed accordingly in order to account for dimensional errors due to the 3D printer.

The third iteration was the final case and it successfully integrated all of the previous design changes. The rapid prototyping capability that 3D printing allows for made it possible to test the case for fits and change the design accordingly until a proper one was made.

Game Testing:

All games were originally created and tested on our PCs using windowed pygame tools. After functional completion of the games, they were then reformatted to work on the PiTFT and incorporated the on-board buttons and joystick. When developing on our laptops, we used our keyboard as an alternative input when testing the games using the pygame libraries, but when moving to the Pi, we needed to change those inputs to the corresponding GPIO pins of the PiTFT buttons. Each game was created so that it can be launched in a stand alone state as well as be launched using the main program.

Incrementally testing our games was a large part of the development process as we needed to understand how each piece of hardware worked with the libraries. One instance of this was when we were figuring out how the joystick position was input. For this, we pulled a piece of sample code from the corresponding sparkfun library, and determined that to incorporate the movements that we desired, we must use the vertical axis of the joystick, and set thresholds on each side to map to the onscreen movement, as the neutral position of the joystick was 512, and ranged between 0 and 1024.

Another aspect that was pivotal in getting the desired outcome was testing the speed of the components on screen and determining what amount of time we wanted the program to sleep so that the desired speed was met. In addition to the speed of the components on screen, we also needed to incorporate sleep statements after button presses for debounce purposes. The amount of debounce was determined through trial and error.

To ensure that the controls of our arcade device was comfortable and intuitive, we personally tested each game throughout the development process as well as had peers test the controls of the games to determine if any of them needed to be altered. This user testing was done in an incremental manner while we were creating each game in addition to final user testing after the game suite was completed.


Drawings and Parts List

Mechanical Drawings:

Assembly Part 1 - Attach Bottom and Middle Plates

Assembly Part 2 - Put Brass Inserts Into Middle Plate Mounting Holes

Assembly Part 3 - Attaching RPi and Joystick Module to Middle Plate

Assembly Part 4 - Attaching Top Plate and PiTFT Buttons

Parts List & Budget:

Part

Part Number

Quantity

Total Price

Additional Description

URL

Raspberry Pi 4

-

1

$0.00

Included

-

PiTFT

-

1

$0.00

Included

-

Wire & Solder

-

-

$0.00

Included in lab.

-

PLA

-

-

$0.00

For 3D printed case - included in lab.

-

Joystick Module

Sparkfun: COM-15168

1

$11.50

I2C

Link

LiPo Battery Module & Charging Cable

Amazon: HYD009

1

$13.99

Power

Link

M3 x 0.5mm x 10mm Screws

McMaster: 92000A120

1 (100 pack)

$6.22

-

Link

M3 x 0.5mm x 5mm Screws

McMaster: 92000A114

1 (100 pack)

$5.45

-

Link

M2.5 x 0.45mm x 5mm Screws

McMaster: 92000A103

1 (100 pack)

$4.63

-

Link

M2.5 x 0.45mm x 16mm Hex Standoffs

McMaster: 98952A113

3

$6.33

-

Link

M2.5 x 0.45mm x 5mm Hex Standoffs

McMaster: 98952A101

3

$5.64

-

Link

Total Price

-

-

$53.76

-

-

Electrical Schematics:

Product Schematic


Results, Conclusion, and Future Work:

Picture showing printed case and joystick. We used a Ultimaker S5 3D printer with PLA filament

Main menu screen where player can select one of the three games - Brick Breaker, Tetris, Pong

From right to left: Brick Breaker, Tetris, and Pong

We were able to implement three different games, Tetris, Brick Breaker, and Pong using the pygame library. We were also able to integrate the raspberry pi with hardware such as a 3d printed case, and joystick for control.

For future work, we thought of the following list of additional functionalities:


References:

PyGame Library: https://www.pygame.org/docs/

Sparkfun Joystick Library: https://learn.sparkfun.com/tutorials/qwiic-joystick-hookup-guide/all

ECE 5725 Lecture Notes

Raspberry Pi Official Specification and Documentation: https://www.raspberrypi.com/documentation/

PiTFT Official Specification and Documentation: https://learn.adafruit.com/adafruit-pitft-28-inch-resistive-touchscreen-display-raspberry-pi/downloads

Pygame Integration and Game Design: https://www.techwithtim.net/tutorials/game-development-with-python/

Header Image Reference: http://assets.stickpng.com/images/585fd0e7cb11b227491c35d0.png


Work Distribution:

                           


Ian Ress                           Alex Kluver              Richard Seibert

3D printed case                            Tetris Gameplay                          Main Game Launcher

Hardware integration                  Multi-game integration                  Pong Gameplay

Overall Device Design                  Device controls(buttons, joystick)    Brick Breaker Gameplay


Code Appendix:

Main.py

import time
import pygame
import os
# game modules
from brick_breaker_game import brickbreaker
from tetris_game import tetris2
from pong_game import pong
import RPi.GPIO as GPIO
import sys
import qwiic_joystick

GPIO.setmode(GPIO.BCM)
GPIO.setup(
17,GPIO.IN,pull_up_down = GPIO.PUD_UP)
GPIO.setup(
22,GPIO.IN,pull_up_down = GPIO.PUD_UP)
GPIO.setup(
23,GPIO.IN,pull_up_down = GPIO.PUD_UP)
GPIO.setup(
27,GPIO.IN,pull_up_down = GPIO.PUD_UP)

image_size = (
240,100)


#os.environ["DISPLAY"] = ":0"
'''
while True:
   print(myJoystick.horizontal)
'''

def main():
   
#joystick initialization
   myJoystick = qwiic_joystick.QwiicJoystick()
   myJoystick.begin()

   
# constants
   SCREEN_SIZE = (
240, 320)
   WHITE = (
255, 255, 255)
   BLACK = (
0, 0, 0)
   
   
# setup main menu screen where you can select what game to play
   pygame.init()
   screen = pygame.display.set_mode(SCREEN_SIZE)

   
# create game objects
   brickBreakerGame = brickbreaker.BrickBreaker(screen, SCREEN_SIZE, myJoystick)
   tetrisGame = tetris2.Tetris()
   pongGame = pong.Pong(screen, SCREEN_SIZE, myJoystick)

   
# main screen buttons
   run =
True
   brickBreakerButton = pygame.Rect(
0, 100, SCREEN_SIZE[0], 100)
   tetrisButton = pygame.Rect(
0, 200, SCREEN_SIZE[0], 100)
   pongButton = pygame.Rect(
0, 300, SCREEN_SIZE[0], 100)
   exitButton = pygame.Rect(
0, 400, SCREEN_SIZE[0], 100)
   buttonsAndGame = [[brickBreakerButton, brickBreakerGame],
                     [tetrisButton, tetrisGame],
                     [pongButton, pongGame]]
   game_choices = [brickBreakerGame, tetrisGame, pongGame]
   
   
'''
   # main screen text
   brickBreakerMenuText = pygame.image.load('assets/brickbreaker_menu_text.png')
   brickBreakerMenuText = pygame.transform.scale(brickBreakerMenuText,image_size)
   tetrisMenuText = pygame.image.load('assets/tetris_menu_text.png')
   tetrisMenuText = pygame.transform.scale(tetrisMenuText,image_size)
   pongMenuText = pygame.image.load('assets/pong_menu_text.png')
   pongMenuText = pygame.transform.scale(pongMenuText,image_size)
   # main control screen
   '''


   white = (
255,255,255)
   green = (
0,255,0)
   black = (
0,0,0)
   font  = pygame.font.Font(
'freesansbold.ttf',32)
   font2 = pygame.font.Font(
None, 18)
   game_choice =
0
   
while run:
       time.sleep(
0.05)
       
       
# check if user tries to close the window
       
'''
       for event in pygame.event.get():
           if event.type == pygame.QUIT:
               run = False
       
       # check for click
       click = pygame.mouse.get_pressed()[0]
       pos = pygame.mouse.get_pos()
       for button, game in buttonsAndGame:
           if button.collidepoint(pos) and click:
               game.run()
       '''

       
for event in pygame.event.get():
           
if event.type == pygame.QUIT:
               
return

       
if myJoystick.horizontal > 1000 and game_choice < 2:
           game_choice +=
1
           time.sleep(
0.5)
       
elif myJoystick.horizontal < 20 and game_choice > 0:
           game_choice -=
1
           time.sleep(
0.5)
       
if not GPIO.input(17):
           
break
       
if not GPIO.input(22):
           game_choices[game_choice].run()
           time.sleep(
1)
       
       brickbreaktext = font.render(
"Brick Breaker", True, white, black)
       tetrisMenuText = font.render(
"Tetris", True, white, black)
       pongMenuText = font.render(
"Pong",True, white, black)
       menuText = font2.render(
"Start              Quit",True, white, black)

       
if game_choice == 0:
           brickbreaktext = font.render(
"Brick Breaker", True, black, white)
       
elif game_choice == 1:
           tetrisMenuText = font.render(
"Tetris", True, black, white)
       
else:
           pongMenuText = font.render(
"Pong", True, black, white)
       
# create main menu text and buttons
       screen.fill(BLACK)

       screen.blit(brickbreaktext, (
0, 0))
       screen.blit(tetrisMenuText, (
0, 100))
       screen.blit(pongMenuText, (
0, 200))
       screen.blit(menuText,(
115,300))
       pygame.display.update()

   
# exit
   pygame.quit()


if __name__ == '__main__':
   main()

Pong.py

import pygame
import time
import RPi.GPIO as GPIO
import qwiic_joystick


class Pong:
   
   
def __init__(self, screen, screen_size, my_joystick):
       
self.joystick = my_joystick
       
self.screen = screen
       
self.screen_size = screen_size
       
self.score = 0
       
self.level = 0
       
self.lives = 0
       
# font stuff
       
self.font = pygame.font.Font(None, 24)
       
self.WHITE = (255, 255, 255)
       
# put the paddle a little above the bottom from the screen
       paddle_pos_y = screen_size[
1]-20
       
self.paddle = pygame.Rect(0, paddle_pos_y, 40, 10)
       
self.paddle_bot = pygame.Rect(0, 20, 40, 10)
       
# ball parameters
       
self.ball = pygame.Rect(20, paddle_pos_y-5, 15, 15)
       
self.ball_speed = [0, 0]
       
self.ball_size = 10
       
self.speed_mod = 1
       
self.ball_stick = True
       
# paddle parameters
       
self.paddle_width = 30
       
self.paddle_height = 10
       
self.reset = False

   
def ball_hit_loc_to_angle(self, x_pos_hit_fraction_raw):
       x_pos_hit_fraction = x_pos_hit_fraction_raw
       
if x_pos_hit_fraction < 0:
           x_pos_hit_fraction =
0
       elif x_pos_hit_fraction >
1:
           x_pos_hit_fraction =
1
       
# get speed of ball depending on where ball hits
       x_speed =
0
       
if x_pos_hit_fraction < 0.2:
           x_speed = -
2
       elif x_pos_hit_fraction <
0.4:
           x_speed = -
1
       elif x_pos_hit_fraction <
0.6:
           x_speed =
0
       elif x_pos_hit_fraction <
0.8:
           x_speed =
1
       
else:
           x_speed =
2
       
return x_speed

   
def ball_logic(self):
       vertical_speed = -
3
       
if self.ball_stick:
           keys = pygame.key.get_pressed()
           
#if keys[pygame.K_SPACE]:
           
if not self.joystick.button:
               
self.ball_stick = False
               
self.ball_speed = [0, vertical_speed+self.speed_mod]
               
return
       
self.ball.left += self.ball_speed[0]
       
self.ball.top  += self.ball_speed[1]
       
# check bounds for the ball left and right wall
       
# left
       
if self.ball.left > self.screen_size[0]-(2*self.ball_size):
           
self.ball_speed[0] = self.ball_speed[0]*-1
           
self.ball.left -= 5
       
# right
       
if self.ball.left < 0:
           
self.ball_speed[0] = self.ball_speed[0]*-1
           
self.ball.left += 5
       
       ball_pos = (
self.ball.left+self.ball_size, self.ball.top+self.ball_size)
       pygame.draw.circle(
self.screen,
                          (
100,100,100),
                          ball_pos,
                         
self.ball_size)
       
# check bounds for the ball top or bottom of the screen
       
# top, player scores
       
if self.ball.top < 0:
           
self.score += 1
           
if self.score%5 == 0:
               
self.level += 1
               
self.speed_mod += 1
               game_over_text =
self.font.render("LEVEL "+str(self.level), False, self.WHITE)
               
self.screen.blit(game_over_text, (self.screen_size[0]/3,
                                                 
self.screen_size[1]/2))
               pygame.display.flip()
               time.sleep(
2)
           
self.reset = True
       
# bottom, bot scores, player loses a life
       elif
self.ball.top > self.screen_size[1]:
           
self.lives -= 1
           
if self.lives <= 0:
               game_over_text =
self.font.render("GAME OVER", False, self.WHITE)
               
self.screen.blit(game_over_text, (self.screen_size[0]/4,
                                                 
self.screen_size[1]/2))
               pygame.display.flip()
               time.sleep(
3)
               
return -1
           
self.reset = True

       
# ball hit paddle logic
       
# bot paddle
       
if self.ball.colliderect(self.paddle_bot):
           
# find ball position relative to paddle to figure out
           
# what x direction speed should be
           x_pos_hit = (
self.ball.left+(self.ball_size/2))-self.paddle_bot.left
           x_pos_hit_fraction = x_pos_hit/
self.paddle_width
           x_speed =
self.ball_hit_loc_to_angle(x_pos_hit_fraction)
           
self.ball_speed = [x_speed, -1*vertical_speed+self.speed_mod]
       
# player paddle
       
if not self.ball_stick and self.ball.colliderect(self.paddle):
           
# find ball position relative to paddle to figure out
           
# what x direction speed should be
           x_pos_hit = (
self.ball.left+(self.ball_size/2))-self.paddle.left
           x_pos_hit_fraction = x_pos_hit/
self.paddle_width
           x_speed =
self.ball_hit_loc_to_angle(x_pos_hit_fraction)
           
self.ball_speed = [x_speed, vertical_speed+self.speed_mod]

   
def paddle_logic(self):
       
# movement
       
#keys = pygame.key.get_pressed()
       paddle_move_speed =
5
       
#if keys[pygame.K_LEFT]:
       
if self.joystick.vertical > 1020:
           
self.paddle.left -= paddle_move_speed
           
if self.ball_stick:
               
self.ball.left -= paddle_move_speed
       
#elif keys[pygame.K_RIGHT]:
       elif
self.joystick.vertical < 5:
           
self.paddle.left += paddle_move_speed
           
if self.ball_stick:
               
self.ball.left += paddle_move_speed
       
# bounds check
       
if self.paddle.left < 0:
           
self.paddle.left = 0
       elif
self.paddle.right > self.screen_size[0]-10:
           
self.paddle.right = self.screen_size[0]-10
       
#draw
       pygame.draw.rect(
self.screen, (200, 0, 200), self.paddle)

   
def paddle_bot_logic(self):
       paddle_move_speed =
1+int(self.level/10)
       
# bot movement
       
if self.ball.left+(self.ball_size/2) < (self.paddle_bot.left+self.paddle_width):
           
self.paddle_bot.left -= paddle_move_speed
       
else:
           
self.paddle_bot.left += paddle_move_speed
       
# bounds check
       
if self.paddle_bot.left < 0:
           
self.paddle_bot.left = 0
       elif
self.paddle_bot.right > self.screen_size[0]-10:
           
self.paddle_bot.right = self.screen_size[0]-10
       
#draw
       pygame.draw.rect(
self.screen, (100, 100, 0), self.paddle_bot)

   
def run(self):
       print(
'Running pong')
       
# initalize all parameters
       runGame = True
       
self.level = 1
       
self.score = 0
       
self.lives = 3
       
self.reset = False
       
       
# paddle params
       paddlePos =
0
       
# want paddle to be at the bottom of the screen
       paddlePosY =
self.screen_size[1] - 20
       paddle = pygame.Rect(paddlePos, paddlePosY,
self.paddle_width, self.paddle_height)
       
       
# ball params
       
self.speed_mod = 1
       
       BLACK = (
0,0,0)
       
       
# text params
       text_line = pygame.Rect(
0, 14, self.screen_size[0]+10, 2)
       
       
while runGame:
           
self.screen.fill(BLACK)
           
# go back to main menu if user tries to exit
           
for event in pygame.event.get():
               
if event.type == pygame.QUIT:
                   
return
           
           
# draw current level and other text on screen
           font_surface =
self.font.render("Level: " + str(self.level) +
                                     
"   Score: " + str(self.score) +
                                     
"   Lives: " + str(self.lives),
                                      False,
self.WHITE)
           
self.screen.blit(font_surface, (10, 0))
           pygame.draw.rect(
self.screen, self.WHITE, text_line)
           
           
# ball logic
           ball_return =
self.ball_logic()
           
# game over
           
if ball_return == -1:
               
return
           
# check for button quit
           
if not GPIO.input(17):
               
return
           
# paddle logic
           
self.paddle_logic()

           
# paddle bot logic
           
self.paddle_bot_logic()
           
           
# ball went out of bounds
           
if self.reset:
               
self.reset = False
               
self.ball_stick = True
               
self.ball_speed = [0, 0]
               
self.ball.top = self.paddle.top - self.paddle_height
               
self.ball.left = self.paddle.left + self.paddle_width/2
           
           
# display update
           pygame.display.update()
           pygame.display.flip()

BrickBreaker.py

import pygame
import time
import RPi.GPIO as GPIO
import qwiic_joystick


class BrickBreaker:
   
   
def __init__(self, screen, screen_size, my_joystick):
       
self.joystick = my_joystick
       
self.screen = screen
       
self.screen_size = screen_size
       
# put the paddle a little above the bottom from the screen
       paddle_pos_y = screen_size[
1]-20
       
self.paddle = pygame.Rect(0, paddle_pos_y, 40, 10)
       
# ball parameters
       
self.ball = pygame.Rect(20, paddle_pos_y-5, 15, 15)
       
self.ball_speed = [0, 0]
       
self.ball_size = 10
       
self.speed_mult = 1
       
self.ball_stick = True
       
# paddle parameters
       
self.paddle_width = 30
       
self.paddle_height = 10
       
self.reset = False
       
self.new_level = True
       
# all levels, 1 is where a brick should be
       
self.levelLayouts = [
                     
# level 1
                     [[
1,1,1,1,1,1,1,1,1,1],
                      [
0,0,1,1,1,1,1,1,0,0],
                      [
0,0,0,0,0,0,0,0,0,0],
                      [
0,0,0,0,0,0,0,0,0,0],
                      [
0,0,0,0,0,0,0,0,0,0]],
                     
# level 2
                     [[
0,0,1,0,1,0,1,0,1,0],
                      [
0,1,1,1,1,1,1,1,1,0],
                      [
0,1,0,0,1,0,0,1,0,0],
                      [
0,0,0,0,0,0,0,0,0,0],
                      [
0,0,0,0,0,0,0,0,0,0]],
                     
# level 3
                     [[
0,0,1,1,1,1,1,1,0,0],
                      [
0,1,0,1,1,1,1,0,1,0],
                      [
0,0,1,0,1,1,0,1,0,0],
                      [
0,0,0,0,0,0,0,0,0,0],
                      [
0,0,0,0,0,0,0,0,0,0]],
                     
# level 4
                     [[
0,1,1,1,1,1,1,1,1,0],
                      [
0,1,1,1,1,1,1,1,1,0],
                      [
0,1,1,1,1,1,1,1,1,0],
                      [
0,1,0,1,0,1,0,1,0,0],
                      [
0,0,0,0,0,0,0,0,0,0]],
                     
# level 5
                     [[
1,1,1,1,1,1,1,1,1,1],
                      [
0,1,1,1,1,1,1,1,1,0],
                      [
0,0,1,1,1,1,1,1,0,0],
                      [
0,0,0,1,1,1,1,0,0,0],
                      [
0,0,0,0,1,1,0,0,0,0]]
                     ]

   
def make_level(self, level):
       base_level  = level%
10
       base_colors = [((level+
1)*20)%255, ((level+100)*10)%255, ((level+200)*20)%255]
       levelLayout =
self.levelLayouts[base_level]
       
# layout where the bricks go and count how many
       numberOfBricks =
0
       bricks = []
       
# offset from walls
       offsetX =
5
       offsetY =
20
       brickWidth =
20
       brickHeight =
10
       
# bricks list is a n by 2 array with rect object at idx 0 and color at 1
       
for i in range(len(levelLayout[0])):
           
for j in range(len(levelLayout)):
               
if levelLayout[j][i] == 1:
                   numberOfBricks +=
1
                   bricks.append([pygame.Rect(offsetX+(i*(brickWidth+
3)),
                                              offsetY+(j*(brickHeight+
3)),
                                              brickWidth,
                                              brickHeight),
                                              base_colors])
                   
#pygame.draw.rect(self.screen, baseColors, bricks[i][j])
       
       
return bricks, numberOfBricks
   
   
def ball_logic(self):
       vertical_speed = -
3
       
if self.ball_stick:
           keys = pygame.key.get_pressed()
           
#if keys[pygame.K_SPACE]:
           
if not self.joystick.button:
               
self.ball_stick = False
               
self.ball_speed = [0, vertical_speed]
               
return
       
self.ball.left += self.ball_speed[0]
       
self.ball.top  += self.ball_speed[1]
       
# check bounds for the ball left and right wall
       
if self.ball.left < 0:
           
self.ball_speed[0] = self.ball_speed[0]*-1
           
self.ball.left += 5
       
if self.ball.left > self.screen_size[0]-(2*self.ball_size):
           
self.ball_speed[0] = self.ball_speed[0]*-1
           
self.ball.left -= 5

       
# top
       
if self.ball.top < 0:
           
self.ball_speed[1] = self.ball_speed[1]*-1
       
# bottom
       elif
self.ball.top > self.screen_size[1]:
           
self.reset = True
       
       ball_pos = (
self.ball.left+self.ball_size, self.ball.top+self.ball_size)
       pygame.draw.circle(
self.screen,
                          (
100,100,100),
                          ball_pos,
                         
self.ball_size)

       
# ball hit paddle logic
       
if not self.ball_stick and self.ball.colliderect(self.paddle):
           
# find ball position relative to paddle to figure out
           
# what x direction speed should be
           x_pos_hit = (
self.ball.left+(self.ball_size/2))-self.paddle.left
           x_pos_hit_fraction = x_pos_hit/
self.paddle_width
           
if x_pos_hit_fraction < 0:
               x_pos_hit_fraction =
0
           elif x_pos_hit_fraction >
1:
               x_pos_hit_fraction =
1
           
# get speed of ball depending on where ball hits
           x_speed =
0
           
if x_pos_hit_fraction < 0.2:
               x_speed = -
2
           elif x_pos_hit_fraction <
0.4:
               x_speed = -
1
           elif x_pos_hit_fraction <
0.6:
               x_speed =
0
           elif x_pos_hit_fraction <
0.8:
               x_speed =
1
           
else:
               x_speed =
2
           
self.ball_speed = [x_speed, vertical_speed+self.speed_mult]

   
def paddle_logic(self):
       
# movement
       
#keys = pygame.key.get_pressed()
       paddle_move_speed =
4
       
#if keys[pygame.K_LEFT]:
       
if self.joystick.vertical > 1020:
           
self.paddle.left -= paddle_move_speed
           
if self.ball_stick:
               
self.ball.left -= paddle_move_speed
       
#elif keys[pygame.K_RIGHT]:
       elif
self.joystick.vertical < 5:
           
self.paddle.left += paddle_move_speed
           
if self.ball_stick:
               
self.ball.left += paddle_move_speed
       
# bounds check
       
if self.paddle.left < 0:
           
self.paddle.left = 0
       elif
self.paddle.right > self.screen_size[0]-10:
           
self.paddle.right = self.screen_size[0]-10
       
#draw
       pygame.draw.rect(
self.screen, (200, 0, 200), self.paddle)
   
   
def run(self):
       print(
'Running brick breaker')
       
# initalize all parameters
       runGame = True
       level =
0
       
self.reset = False
       
       
# paddle params
       paddlePos =
0
       
# want paddle to be at the bottom of the screen
       paddlePosY =
self.screen_size[1] - 20
       paddle = pygame.Rect(paddlePos, paddlePosY,
self.paddle_width, self.paddle_height)
       
       
# ball params
       
self.speed_mult = 1
       
       
# brick params
       bricks = []
       bricks_left =
0
       BLACK = (
0,0,0)
       WHITE = (
255, 255, 255)
       
       
# text params
       font = pygame.font.Font(None,
24)
       level =
0
       score =
0
       lives =
3
       text_line = pygame.Rect(
0, 14, self.screen_size[0]+10, 2)
       
       
while runGame:
           
self.screen.fill(BLACK)
           
# go back to main menu if user tries to exit
           
for event in pygame.event.get():
               
if event.type == pygame.QUIT:
                   
return
           
           
# make new bricks for next level if there's no more bricks left
           
if bricks_left == 0:
               
# got through all 10 levels (or starting a new game),
               
# speed up ball and do something else
               
if level%5 == 0 and level != 0:
                   
self.speed_mult += 1
               bricks, bricks_left =
self.make_level(level)
               
self.ball.left = 50
               
self.ball.top = 50
               level +=
1
               
self.reset = True
               
self.new_level = True
           
           
# draw bricks, first element is rect and second is color
           
for brick in bricks:
               
if self.ball.colliderect(brick[0]):
                   bricks.remove(brick)
                   score +=
10
                   
self.ball_speed[1] = self.ball_speed[1]*-1
                   bricks_left -=
1
               
# screen, color, rect object
               pygame.draw.rect(
self.screen, brick[1], brick[0])
           
           
# draw current level and other text on screen
           font_surface = font.render(
"Level: " + str(level) +
                                     
"   Score: " + str(score) +
                                     
"   Lives: " + str(lives),
                                      False, WHITE)
           
self.screen.blit(font_surface, (10, 0))
           pygame.draw.rect(
self.screen, WHITE, text_line)
           
           
# check exit button
           
if not GPIO.input(17):
               
return
           
# ball logic
           
self.ball_logic()
           
# paddle logic
           
self.paddle_logic()
           
           
# ball went out of bounds
           
if self.reset:
               
if not self.new_level:
                   lives -=
1
               
else:
                   
self.new_level = False
               
self.reset = False
               
self.ball_stick = True
               time.sleep(
0.2)
               
self.ball_speed = [0, 0]
               
self.ball.top = self.paddle.top - self.paddle_height
               
self.ball.left = self.paddle.left + self.paddle_width/2
               
if lives == 0:
                   game_over_text = font.render(
"GAME OVER", False, WHITE)
                   
self.screen.blit(game_over_text, (self.screen_size[0]/6,
                                                     
self.screen_size[1]/2))
                   pygame.display.flip()
                   time.sleep(
3)
                   
return
           
           
# display update
           pygame.display.update()
           pygame.display.flip()

Tetris.py

import pygame
import time
import random
import RPi.GPIO as GPIO
import sys
from pygame.locals import *
import os

class Tetris:

   
def run(self):

       pygame.font.init()

       
# GLOBALS VARS
       
#screen size and play areas
       screen_height =
320
       play_area_h =
300

       screen_width =
240
       play_area_w =
150  # meaning 300 // 10 = 30 width per block

       piece_size =
15

       play_area_x = (screen_width - play_area_w) //
2
       play_area_y = screen_height - play_area_h

       
#all possible tetris piece configurations
       Tetris_pieces = [
           [[
'.....',
             
'.....',
             
'..00.',
             
'.00..',
             
'.....'],
            [
'.....',
             
'..0..',
             
'..00.',
             
'...0.',
             
'.....']],
           [[
'.....',
             
'.....',
             
'.00..',
             
'..00.',
             
'.....'],
            [
'.....',
             
'..0..',
             
'.00..',
             
'.0...',
             
'.....']],
           [[
'..0..',
             
'..0..',
             
'..0..',
             
'..0..',
             
'.....'],
            [
'.....',
             
'0000.',
             
'.....',
             
'.....',
             
'.....']],      
           [[
'.....',
             
'.....',
             
'.00..',
             
'.00..',
             
'.....']],
           [[
'.....',
             
'.0...',
             
'.000.',
             
'.....',
             
'.....'],
            [
'.....',
             
'..00.',
             
'..0..',
             
'..0..',
             
'.....'],
            [
'.....',
             
'.....',
             
'.000.',
             
'...0.',
             
'.....'],
            [
'.....',
             
'..0..',
             
'..0..',
             
'.00..',
             
'.....']],
           [[
'.....',
             
'...0.',
             
'.000.',
             
'.....',
             
'.....'],
            [
'.....',
             
'..0..',
             
'..0..',
             
'..00.',
             
'.....'],
            [
'.....',
             
'.....',
             
'.000.',
             
'.0...',
             
'.....'],
            [
'.....',
             
'.00..',
             
'..0..',
             
'..0..',
             
'.....']],
           [[
'.....',
             
'..0..',
             
'.000.',
             
'.....',
             
'.....'],
            [
'.....',
             
'..0..',
             
'..00.',
             
'..0..',
             
'.....'],
            [
'.....',
             
'.....',
             
'.000.',
             
'..0..',
             
'.....'],
            [
'.....',
             
'..0..',
             
'.00..',
             
'..0..',
             
'.....']]]
       tetris_colors = [(
0, 123, 98), (222, 100, 0), (128, 0, 255), (255, 0, 0), (0, 165, 0), (0, 0, 255), (100, 50, 100)]


       
class Tetris_shape(object):  # *
           
def __init__(self, x, y, tetris_piece):
               self.x = x
               self.y = y
               self.shape = tetris_piece
               
#set initial orientation
               self.orientation =
0
               
#choose color corresponding to piece
               self.color = tetris_colors[Tetris_pieces.index(tetris_piece)]


       
def mapped_positions(static_position={}):
           map = []
           
for i in range(20):
               map.append([(
0, 0, 0),
                           (
0, 0, 0),
                           (
0, 0, 0),
                           (
0, 0, 0),
                           (
0, 0, 0),
                           (
0, 0, 0),
                           (
0, 0, 0),
                           (
0, 0, 0),
                           (
0, 0, 0),
                           (
0, 0, 0)])
           
for i in range(len(map)):
               
for j in range(len(map[i])):
                   
if (j, i) in static_position:
                       map[i][j] = static_position[(j,i)]
           
return map


       
def shape_shift(shape):
           positions = []
           shift = shape.shape[shape.orientation % len(shape.shape)]

           
for i, line in enumerate(shift):
               row = list(line)
               
for j, column in enumerate(row):
                   
if column == '0':
                       positions.append((shape.x + j, shape.y + i))

           
for i, pos in enumerate(positions):
               positions[i] = (pos[
0] - 2, pos[1] - 4)

           
return positions


       
def in_bounds(shape, map):
           
           open_positions = []
           open_positions_rows = []
           
for j in range(10):
               open_positions_rows = []
               
for i in range(20):
                   
if map[i][j] == (0,0,0):
                       open_positions_rows.append((j,i))
               open_positions.append(open_positions_rows)
           open_positions = [j
for i in open_positions for j in i]

           shifted = shape_shift(shape)

           
for pos in shifted:
               
if pos not in open_positions:
                   
if pos[1] > -1:
                       
return False
           
return True

       
def remove_full_rows(map, static_position):

           score_increase =
0
           
for i in range(len(map)-1, -1, -1):
               row = map[i]
               
if (0,0,0) not in row:
                   score_increase +=
1
                   index = i
                   
for j in range(len(row)):
                       
try:
                           
del static_position[(j,i)]
                       
except:
                           
continue

           
if score_increase > 0:
               
for key in sorted(list(static_position), key=lambda x: x[1])[::-1]:
                   x, y = key
                   
if y < index:
                       n = (x, y + score_increase)
                       static_position[n] = static_position.pop(key)

           
return score_increase


       
def main(win):

           static_pos = {}
           map = mapped_positions(static_pos)
           lost =
False
           diff_shape =
False
           run =
True
           cur_shape = Tetris_shape(
5,0,random.choice(Tetris_pieces))
           n_shape = Tetris_shape(
5,0,random.choice(Tetris_pieces))
           downtime =
0
           downspeed =
0.30
           score =
0
           clock = pygame.time.Clock()
           
while run:
               map = mapped_positions(static_pos)
               downtime += clock.get_rawtime()
               clock.tick()

               
if downtime/1000 > downspeed:
                   downtime =
0
                   cur_shape.y +=
1
                   
if not(in_bounds(cur_shape, map)) and cur_shape.y > 0:
                       cur_shape.y -=
1
                       diff_shape =
True
               
               
#move left
               
if not GPIO.input(27):
                   
if not GPIO.input(17):
                       run =
False
                   cur_shape.x -=
1
                   
#if out of bounds, move back
                   
if not(in_bounds(cur_shape, map)):
                       cur_shape.x +=
1
               
#move right
               
if not GPIO.input(23):
                   cur_shape.x +=
1
                   
#if out of bounds, move back
                   
if not(in_bounds(cur_shape, map)):
                       cur_shape.x -=
1
               
#move down
               
if not GPIO.input(17):
                   cur_shape.y +=
1
                   
#if out of bounds, move back
                   
if not(in_bounds(cur_shape, map)):
                       cur_shape.y -=
1
               
#change piece orientation
               
if not GPIO.input(22):
                   cur_shape.orientation +=
1
                   
#make sure shift doesnt cause piece to go out of bounds
                   
if not(in_bounds(cur_shape, map)):
                       cur_shape.orientation -=
1
               
#button debounce
               time.sleep(
0.1)
               shape_pos = shape_shift(cur_shape)

               
for i in range(len(shape_pos)):
                   x, y = shape_pos[i]
                   
if y > -1:
                       map[y][x] = cur_shape.color

               
if diff_shape:
                   
for pos in shape_pos:
                       p = (pos[
0], pos[1])
                       static_pos[p] = cur_shape.color
                   cur_shape = n_shape
                   n_shape = Tetris_shape(
5,0,random.choice(Tetris_pieces))
                   diff_shape =
False
                   score += remove_full_rows(map, static_pos) *
10

               
#print new screen
               win.fill((
0, 0, 0))

               
for i in range(len(map)):
                   
for j in range(len(map[i])):
                       pygame.draw.rect(win, map[i][j], (play_area_x + j*piece_size, play_area_y + i*piece_size, piece_size, piece_size),
0)
               font = pygame.font.Font(
None,24)
               font_surface = font.render(
"Score:"+ str(score),False, (255,255,255))
               win.blit(font_surface,(
60,0))
               pygame.draw.rect(win, (
128, 128, 128), (play_area_x, play_area_y, play_area_w, play_area_h), 2)
               pygame.display.update()


               
for i in static_pos:
                   x_pos, y_pos = i
                   
if y_pos < 1:
                       lost =
True
               
if lost:

                   pygame.display.update()
                   pygame.time.delay(
1500)
                   run =
False
               time.sleep(
0.1)
           
return score


       screen = pygame.display.set_mode((screen_width, screen_height))
       score = main(screen)
       screen.fill((
0, 0, 0))
       font = pygame.font.Font(
None,32)
       font_surface = font.render(
"Game Over",False, (255,255,255))
       screen.blit(font_surface,(
60,50))
       font_surface = font.render(
"Score:" + str(score),False, (255,255,255))
       screen.blit(font_surface,(
60,140))
       font = pygame.font.Font(
None,20)
       font_surface = font.render(
"QUIT",False, (255,255,255))
       screen.blit(font_surface,(
60,300))
       pygame.display.update()
       
while True:
           
if not GPIO.input(23):
               
break
       
return

Joystick_test.py

import qwiic_joystick
import time
import sys


myJoystick = qwiic_joystick.
QwiicJoystick()

if myJoystick.connected == False:
   
print("JoyStick not connected")


myJoystick.begin()

print("initialized")

while True:

   
print(myJoystick.vertical)

   time.sleep(
0.5)